﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Cci;

using PartialExpressionCompleter.Util;

namespace PartialExpressionCompleter.Query
{
    static class ExpressionUtil
    {
        public static Expression AsExpression<E>(this IExpression ie, QueryContext<E> context) where E : IExpression
        {
            var res = ie._AsExpression(context);
            if (res.Type is Dummy)
            {
                throw new Exception(res + " has dummy type.");
            }
            if (context != null)
            {
                res.Meanings = context.GetExpressionMeanings(ie);
            }
            return res;
        }
        private static Expression AsExpression<E>(this IExpression ie,
                                               ITypeReference type,
                                               QueryContext<E> context) where E : IExpression
        {
            bool ieIsInt = ie.Type.ToString().Equals("System.Int32");
            if (ieIsInt && ie is ICompileTimeConstant)
            {
                if (type.ToString().Equals("System.Boolean"))
                {
                    return new CompileTimeConstant(type.ResolvedType,
                        (ie as ICompileTimeConstant).Value.ToString().Equals("0")
                            ? false
                            : true);
                }
                else if (type.IsEnum)
                {
                    // TODO display enum constants?
                    var enumType = type.ResolvedType;
                    return new CompileTimeConstant(enumType,
                        enumType.GetEnumValue((ie as ICompileTimeConstant).Value));
                }
            }
            else if (ie.Type.ToString().Equals("System.Object")
                && ie is ICompileTimeConstant
                && (ie as ICompileTimeConstant).Value == null)
            {
                return new CompileTimeConstant(type.ResolvedType, null);
            }
            if (!type.IsAssignableFrom(ie.Type, false))
            {
                if (ie.Type is IManagedPointerTypeReference
                    && (ie.Type as IManagedPointerTypeReference).TargetType.ToString()
                        .Equals(type.ToString()))
                {
                    return new UnsupportedCompleteExpression(ie, type);
                }
                else if (ieIsInt && ie is IConditional && type.IsEnum)
                {
                    var cond = ie as IConditional;
                    return new Conditional(cond.Condition.AsExpression(context),
                        cond.ResultIfTrue.AsExpression(type, context),
                        cond.ResultIfFalse.AsExpression(type, context),
                        type.ResolvedType);
                }
                else if (ieIsInt && !type.TypeCode.Equals(PrimitiveTypeCode.NotPrimitive))
                {
                    return ie.AsExpression(context);
                }
                else
                {
                    return new UnsupportedCompleteExpression(ie, type);
                }
            }
            else
            {
                return ie.AsExpression(context);
            }
        }
        private static Expression _AsExpression<E>(this IExpression ie, QueryContext<E> context) where E : IExpression
        {
            if (ie is ILocalDefinition)
            {
                return new LocalVariable(ie as ILocalDefinition);
            }
            else if (ie is IMethodCall)
            {
                var call = ie as IMethodCall;
                var method = call.MethodToCall;
                var thisArg = call.ThisArgument;
                var args = call.Arguments;
                var parameters = call.MethodToCall.ResolvedMethod.GetParameterTypes();
                if (method.HasThisParameter())
                {
                    if (thisArg is IAddressOf)
                    {
                        var thisAdd = thisArg as IAddressOf;
                        thisArg = thisAdd.Expression;
                    }
                    args = thisArg.Cons(args);
                }
                var argExpList = args.Zip(parameters, (a, p) => a.AsExpression(p, context))
                                     .ToList();
                if (method.Name.ToString().StartsWith("get_"))
                {
                    string propName = method.Name.ToString().Substring(4);
                    if (method.HasThisParameter())
                    {
                        return new PropertyLookup(argExpList[0],
                            method.Type.ResolvedType, propName, false);
                    }
                    else
                    {
                        return new StaticPropertyLookup(method.ContainingType.ResolvedType,
                            method.Type.ResolvedType, propName, false);
                    }
                }
                return new MethodCall(method, argExpList, true);
            }
            else if (ie is ICompileTimeConstant)
            {
                var c = ie as ICompileTimeConstant;
                var t = c.Type.ResolvedType;
                if (c.Value is string)
                {
                    return new StringCompileTimeConstant(t, c.Value as string);
                }
                else if (t.TypeCode.Equals(PrimitiveTypeCode.Int32))
                {
                    var valInt = c.Value as System.Int32?;
                    if (valInt >= System.Byte.MinValue && valInt <= System.Byte.MaxValue)
                    {
                        return new CompileTimeConstant(CCIUtil.GetType("System.Byte"), c.Value);
                    }
                    else if (valInt >= System.Int16.MinValue && valInt <= System.Int16.MaxValue)
                    {
                        return new CompileTimeConstant(CCIUtil.GetType("System.Int16"), c.Value);
                    }
                }
                return new CompileTimeConstant(t, c.Value);
            }
            else if (ie is IBoundExpression || ie is IAddressableExpression
                  || ie is ITargetExpression)
            {
                object definition;
                IExpression instance;
                if (ie is IBoundExpression)
                {
                    var b = ie as IBoundExpression;
                    definition = b.Definition;
                    instance = b.Instance;
                }
                else if (ie is IAddressableExpression)
                {
                    var a = ie as IAddressableExpression;
                    definition = a.Definition;
                    instance = a.Instance;
                }
                else if (ie is ITargetExpression)
                {
                    var t = ie as ITargetExpression;
                    definition = t.Definition;
                    instance = t.Instance;
                }
                else
                {
                    throw new Exception("Forgotten IBoundExpression-like type: "
                        + ie.GetType());
                }
                if (definition is ILocalDefinition)
                {
                    return new LocalVariable(definition as ILocalDefinition);
                }
                else if (definition is IParameterDefinition)
                {
                    return new ParamVariable(definition as IParameterDefinition);
                }
                else if (definition is IFieldDefinition
                    || definition is IFieldReference)
                {
                    IFieldDefinition f;
                    if (definition is IFieldReference)
                    {
                        f = (definition as IFieldReference).ResolvedField;
                    }
                    else
                    {
                        f = definition as IFieldDefinition;
                    }
                    if (instance == null)
                    {
                        return new StaticFieldLookup(f);
                    }
                    else
                    {
                        return new FieldLookup(instance.AsExpression(context), f);
                    }
                }
                else if (definition is IArrayIndexer)
                {
                    var ai = definition as IArrayIndexer;
                    return ai.AsExpression(context);
                }
                else if (definition is IAddressDereference)
                {
                    var ad = definition as IAddressDereference;
                    return ad.AsExpression(context);
                }
                else
                {
                    throw new NotImplementedException(
                        "Unknown IBoundExpression type: " + ie.GetType()
                        + ", definition type: " + definition.GetType());
                }
            }
            else if (ie is ICreateDelegateInstance)
            {
                var del = ie as ICreateDelegateInstance;
                return new CreateDelegate(ie.Type.ResolvedType,
                                          del.Instance.AsExpression(context),
                                          del.MethodToCallViaDelegate);
            }
            else if (ie is IConversion)
            {
                var c = ie as IConversion;
                return new Cast(c.Type.ResolvedType, c.ValueToConvert.AsExpression(context));
            }
            else if (ie is IThisReference)
            {
                var t = ie as IThisReference;
                return new This(t.Type.ResolvedType);
            }
            else if (ie is IBinaryOperation)
            {
                var op = ie as IBinaryOperation;
                string opStr;
                if (op is IAddition)
                {
                    opStr = "+";
                }
                else if (op is ISubtraction)
                {
                    opStr = "-";
                }
                else if (op is IMultiplication)
                {
                    opStr = "*";
                }
                else if (op is IDivision)
                {
                    opStr = "/";
                }
                else if (op is IModulus)
                {
                    opStr = "%";
                }
                else if (op is IBitwiseAnd)
                {
                    opStr = "&";
                }
                else if (op is IBitwiseOr)
                {
                    opStr = "|";
                }
                else if (op is IEquality)
                {
                    opStr = "==";
                }
                else if (op is INotEquality)
                {
                    opStr = "!=";
                }
                else if (op is ILessThan)
                {
                    opStr = "<";
                }
                else if (op is ILessThanOrEqual)
                {
                    opStr = "<=";
                }
                else if (op is IGreaterThanOrEqual)
                {
                    opStr = ">=";
                }
                else if (op is IGreaterThan)
                {
                    opStr = ">";
                }
                else
                {
                    opStr = "???";
                }
                return new BinaryOperation(op.LeftOperand.AsExpression(context),
                                           op.RightOperand.AsExpression(context),
                                           opStr, op.Type.ResolvedType);
            }
            else if (ie is IUnaryOperation)
            {
                var op = ie as IUnaryOperation;
                string opStr;
                if (op is ILogicalNot)
                {
                    opStr = "!";
                }
                else if (op is IUnaryNegation)
                {
                    opStr = "~";
                }
                else
                {
                    opStr = "???";
                }
                return new UnaryOperation(op.Operand.AsExpression(context),
                                          opStr,
                                          op.Type.ResolvedType);
            }
            else if (ie is IAddressDereference)
            {
                return new UnaryOperation((ie as IAddressDereference).Address.AsExpression(context),
                                          "*", ie.Type.ResolvedType);
            }
            else if (ie is IAnonymousDelegate)
            {
                return new Closure(ie as IAnonymousDelegate);
            }
            else if (ie is IArrayIndexer)
            {
                return ArrayLookup.Create(ie as IArrayIndexer, context);
            }
            else if (ie is IAddressOf)
            {
                return AddressOf.Create(ie as IAddressOf, context);
            }
            else if (ie is ICreateObjectInstance)
            {
                var co = ie as ICreateObjectInstance;
                return new MethodCall(co.MethodToCall,
                    co.Arguments.Select(a => a.AsExpression(context)).ToList(), true,
                    ie.Type.ResolvedType);
            }
            else if (ie is ITypeOf)
            {
                return new TypeOf(ie as ITypeOf);
            }
            else if (ie is ICreateArray)
            {
                return CreateArray.Create(ie as ICreateArray, context);
            }
            else if (ie is IVectorLength)
            {
                return new PropertyLookup((ie as IVectorLength).Vector.AsExpression(context),
                                          ie.Type.ResolvedType, "Length", false);
            }
            else if (ie is IConditional)
            {
                var cond = ie as IConditional;
                var condition = cond.Condition.AsExpression(context);
                var trueBranch = cond.ResultIfTrue.AsExpression(context);
                var falseBranch = cond.ResultIfFalse.AsExpression(context);

                return new Conditional(condition, trueBranch, falseBranch,
                                       cond.Type.ResolvedType);
            }
            else
            {
                return new UnsupportedCompleteExpression(ie);
            }
        }

        public static Expression WithBaseCost(this Expression exp, int baseCost)
        {
            exp.BaseCost = baseCost;
            return exp;
        }
    }
}
